package com.example.lte.config;

import cn.dev33.satoken.stp.StpUtil;
import com.example.lte.entity.OperationLogEntity;
import com.example.lte.service.IAsyncService;
import com.example.lte.util.GeolocationUtil;
import com.example.lte.util.IpUtils;
import lombok.extern.log4j.Log4j2;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;

/**
 * ReqAspect Controller请求切面，获取请求者的各项信息
 *
 * 为什么需要AOP
 * AOP（Aspect Orient Programming）也就是面向切面编程，作为面向对象编程的一种补充，已经成为一种比较成熟的编程方式。其实AOP问世的时间并不太长，AOP和OOP互为补充，面向切面编程将程序运行过程分解成各个切面。
 * AOP专门用于处理系统中分布于各个模块（不同方法）中的交叉关注点的问题，在JavaEE应用中，常常通过AOP来处理一些具有横切性质的系统级服务，如事务管理、安全检查、缓存、对象池管理等，AOP已经成为一种非常常用的解决方案。
 *
 * 使用AspectJ实现AOP
 * AspectJ是一个基于Java语言的AOP框架，提供了强大的AOP功能，其他很多AOP框架都借鉴或采纳其中的一些思想。其主要包括两个部分：一个部分定义了如何表达、定义AOP编程中的语法规范，通过这套语法规范，可以方便地用AOP来解决Java语言中存在的交叉关注点的问题；另一个部分是工具部分，包括编译、调试工具等。
 *
 * AOP实现可分为两类：
 * 静态AOP实现: AOP框架在编译阶段对程序进行修改，即实现对目标类的增强，生成静态的AOP代理类，以AspectJ为代表。
 * 动态AOP实现: AOP框架在运行阶段动态生成AOP代理，以实现对目标对象的增强，以Spring AOP为代表。
 * 一般来说，静态AOP实现具有较好的性能，但需要使用特殊的编译器。动态AOP实现是纯Java实现，因此无须特殊的编译器，但是通常性能略差。
 *
 * AOP的基本概念
 * 关于面向切面编程的一些术语：
 * 切面（Aspect）: 切面用于组织多个Advice，Advice放在切面中定义。
 * 连接点（Joinpoint）: 程序执行过程中明确的点，如方法的调用，或者异常的抛出。在Spring AOP中，连接点总是方法的调用。
 * 增强处理（Advice）: AOP框架在特定的切入点执行的增强处理。处理有"around"、"before"和"after"等类型
 * 切入点（Pointcut）: 可以插入增强处理的连接点。简而言之，当某个连接点满足指定要求时，该连接点将被添加增强处理，该连接点也就变成了切入点。
 * Spring的AOP支持
 * Spring中的AOP代理由Spring的IoC容器负责生成、管理，其依赖关系也由IoC容器负责管理。
 * 为了在应用中使用@AspectJ支持，Spring需要添加三个库：
 * aspectjweaver.jar
 * aspectjrt.jar
 * aopalliance.jar
 *
 * @author lzh
 * @date 2020/9/21 - 10:13
 */
@Aspect
@Order(1)
// 控制多个Aspect的执行顺序，越小越先执行
@Component
@Log4j2
public class ReqAspect {

    @Autowired
    private IAsyncService iAsyncService;

    //环绕通知(可以获得请求前和响应后的数据)
    //切面为cn.gem.rainbow.api包下的类
    @Around(value = "execution(* com.example.lte.api..*.*(..))")
    public Object handleControllerMethod(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("*********AOP start******* order=1 *************************");
        long startTime = System.currentTimeMillis();

        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        OperationLogEntity logEntity = new OperationLogEntity();
        if (attributes != null) {

            HttpServletRequest request = attributes.getRequest();
            String reqIp = IpUtils.getIpAddr(request);

            log.info("请求IP:{}", reqIp);
            log.info("请求Port:{}", request.getRemotePort());
            log.info("请求URI:{}", request.getRequestURI());
            String token = request.getHeader("Authorization");
            log.info("ReqTokenHeader:{}", token);
            String account = "";
            if (!StringUtils.isEmpty(token)) {
                account = (String) StpUtil.getLoginIdByToken(token);
                log.info("操作人:{}", account);
            }
            String reqLocation = GeolocationUtil.getAddrByIp(reqIp);
            log.warn("请求位置:{}",reqLocation);
            logEntity.setReqLocation(reqLocation);
            logEntity.setAccount(account);
            logEntity.setReqIp(reqIp);
            logEntity.setReqUrl(request.getRequestURI());
            logEntity.setReqToken(token);


        }

        // 获取请求入参
        Object[] args = proceedingJoinPoint.getArgs();
        Arrays.stream(args).forEach(arg -> log.info("arg is {}", arg));

        // 获取响应
        Object response = proceedingJoinPoint.proceed();

        Signature signature = proceedingJoinPoint.getSignature();
        log.info("请求目标类:{}", signature);

        if (signature instanceof MethodSignature) {
            MethodSignature methodSignature = (MethodSignature) signature;
            //获取方法上加的自定义注解 @BusinessLogic 的属性
            BusinessLogic businessLogic = methodSignature.getMethod().getAnnotation(BusinessLogic.class);
            if (businessLogic != null) {
                logEntity.setBusinessUnit(businessLogic.businessUnit().getBusinessDesc());
                logEntity.setBusinessMethod(businessLogic.value());
                log.info("业务操作:{}", businessLogic.value());
                log.info("业务模块:{}", businessLogic.businessUnit().getBusinessDesc());
            }
        }

        long endTime = System.currentTimeMillis();
        log.info("请求耗时:{}ms", (endTime - startTime));
        System.out.println("*******AOP end**********************************");

        //异步保存操作日志
        iAsyncService.saveOperationLogByAsync(logEntity);

        return response;
    }


}
